# 标签打印服务 - 专门适配Brother PT-9500PC
import logging
import json
from typing import Dict, List, Any, Optional
from PIL import Image, ImageDraw, ImageFont
import qrcode
# Optional dependency: python-barcode
try:
    from barcode import Code128
    from barcode.writer import ImageWriter
    HAS_BARCODE = True
except Exception:
    Code128 = None  # type: ignore
    ImageWriter = None  # type: ignore
    HAS_BARCODE = False
import io
import base64
from django.conf import settings
from django.utils import timezone
import os
import subprocess
import tempfile

from .models import Asset
from .models_label import LabelTemplate, LabelPrintJob, PrinterConfiguration, LabelPrintHistory

logger = logging.getLogger(__name__)


class BrotherPT9500PCService:
    """Brother PT-9500PC 标签打印机服务"""
    
    def __init__(self):
        self.printer_model = 'pt-9500pc'
        self.supported_sizes = ['8mm', '12mm', '18mm', '24mm', '36mm']
        
        # 标签尺寸映射 (宽度像素)
        self.size_mapping = {
            '8mm': {'width': 64, 'height': 200, 'dpi': 180},
            '12mm': {'width': 96, 'height': 200, 'dpi': 180},
            '18mm': {'width': 144, 'height': 200, 'dpi': 180},
            '24mm': {'width': 192, 'height': 200, 'dpi': 180},
            '36mm': {'width': 288, 'height': 200, 'dpi': 180},
        }
        
        # 字体大小映射
        self.font_sizes = {
            '8mm': {'small': 8, 'medium': 10, 'large': 12},
            '12mm': {'small': 10, 'medium': 12, 'large': 14},
            '18mm': {'small': 12, 'medium': 14, 'large': 16},
            '24mm': {'small': 14, 'medium': 16, 'large': 18},
            '36mm': {'small': 16, 'medium': 20, 'large': 24},
        }
    
    def create_label_image(self, template: LabelTemplate, asset: Asset, custom_data: Dict = None) -> Image.Image:
        """创建标签图像"""
        
        try:
            # 获取标签尺寸
            size_info = self.size_mapping.get(template.label_size)
            if not size_info:
                raise ValueError(f"不支持的标签尺寸: {template.label_size}")
            
            width = size_info['width']
            height = size_info['height']
            
            # 创建白色背景图像
            image = Image.new('RGB', (height, width), 'white')
            draw = ImageDraw.Draw(image)
            
            # 获取字体
            font_sizes = self.font_sizes.get(template.label_size, self.font_sizes['24mm'])
            
            # 处理字段配置（支持list或dict）
            fields_config_raw = template.fields_config
            fields_config = self._normalize_fields_config(fields_config_raw)
            template_design = template.template_design
            
            # 渲染字段
            self._render_fields(draw, image, fields_config, template_design, asset, custom_data, font_sizes)
            
            return image
            
        except Exception as e:
            logger.error(f"创建标签图像失败: {e}")
            raise
    
    def _normalize_fields_config(self, cfg: Any) -> List[Dict]:
        """将fields_config标准化为字段配置列表。
        支持两种输入：
        1) 列表形式: [{name,label,type,...}]
        2) 字典形式: 包含 include_* 开关、company_header、lost_notice、custom_fields
        """
        try:
            if isinstance(cfg, list):
                return cfg
            result: List[Dict] = []
            if not isinstance(cfg, dict):
                return result

            # 公司抬头、遗失提示（作为纯文本行，无标签）
            if cfg.get('company_header'):
                result.append({'name': 'company_header', 'label': '', 'type': 'text', 'show_label': False, 'font_size': 'large'})
            # 后续在 _get_field_value 里通过 custom_data 取不到时返回空，这里直接用 custom_text 渲染更简单：
            # 为了直接显示内容，把值写到label中，通过 show_label=False 仅显示内容
            # 但渲染函数会从 _get_field_value 取值，这里折中：把字段名仍然叫 company_header，值从 custom_data 或 asset 无需获取

            # 内置字段映射
            include_map = [
                ('include_qr', 'qr_code', '二维码', 'qrcode'),
                ('include_name', 'name', '资产名称', 'text'),
                ('include_number', 'asset_number', '资产编号', 'text'),
                ('include_category', 'category__name', '资产类别', 'text'),
                ('include_department', 'department__name', '所属部门', 'text'),
                ('include_user', 'user__username', '使用人', 'text'),
                ('include_location', 'location__name', '存放位置', 'text'),
                ('include_purchase_date', 'purchase_date', '采购日期', 'date'),
                # 我们的模型为 warranty_period(月)，无到期日期字段
                ('include_warranty', 'warranty_period', '保修期(月)', 'text'),
                ('include_serial_number', 'serial_number', '序列号', 'text'),
                ('include_status', 'status', '资产状态', 'choice'),
                ('include_value', 'current_value', '资产价值', 'decimal'),
            ]
            for include_key, name, label, ftype in include_map:
                if cfg.get(include_key):
                    result.append({'name': name, 'label': label, 'type': ftype, 'show_label': True, 'font_size': 'medium'})

            # 自定义字段
            for item in cfg.get('custom_fields', []) or []:
                n = item.get('name')
                v = item.get('value')
                if n and v is not None:
                    result.append({'name': n, 'label': n, 'type': 'text', 'show_label': True, 'font_size': 'medium'})

            # 遗失提示作为末尾文本
            if cfg.get('lost_notice'):
                result.append({'name': 'lost_notice', 'label': '', 'type': 'text', 'show_label': False, 'font_size': 'small'})

            return result
        except Exception as e:
            logger.error(f"标准化字段配置失败: {e}")
            return []

    def create_brother_pt9500pc_preview(self, template: Optional[LabelTemplate], asset: Optional[Asset], custom_data: Dict, preview_settings: Dict) -> Image.Image:
        """生成Brother PT-9500PC 预览图。
        如果未提供 template，可根据 preview_settings 构建一个临时模板对象。
        """
        try:
            # 如果没有传入模板，则构建临时模板
            if template is None:
                class TempTemplate:
                    def __init__(self, label_size: str):
                        self.label_size = label_size
                        self.template_design = {}
                        # 提供一个最小的 fields_config 以便预览
                        self.fields_config = [
                            {'name': 'name', 'label': '资产名称', 'type': 'text', 'show_label': True, 'font_size': 'medium'},
                            {'name': 'asset_number', 'label': '资产编号', 'type': 'text', 'show_label': True, 'font_size': 'medium'},
                        ]

                template = TempTemplate(preview_settings.get('label_size', '24mm'))

            # 若未提供资产，构造一个最小的占位对象
            if asset is None:
                class TempAsset:
                    def __init__(self):
                        self.asset_number = 'ASSET-001'
                        self.name = '示例设备'
                        self.location = '演示位置'
                        self.serial_number = 'SN-DEMO'
                        self.status = 'in_use'
                        self.current_value = '1000.00'
                asset = TempAsset()

            # 将 company_header 和 lost_notice 通过 custom_data 传入，使渲染能拿到值
            cd = dict(custom_data or {})
            fields_cfg = template.fields_config if isinstance(template.fields_config, (list, dict)) else {}
            if isinstance(fields_cfg, dict):
                if fields_cfg.get('company_header'):
                    cd['company_header'] = fields_cfg.get('company_header')
                if fields_cfg.get('lost_notice'):
                    cd['lost_notice'] = fields_cfg.get('lost_notice')
                # 自定义字段的值也放入 custom_data，便于 _get_field_value 获取
                for item in fields_cfg.get('custom_fields', []) or []:
                    if item.get('name'):
                        cd[item['name']] = item.get('value', '')

            return self.create_label_image(template, asset, cd)
        except Exception as e:
            logger.error(f"创建Brother PT-9500PC预览失败: {e}")
            raise
    
    def _render_fields(self, draw: ImageDraw.Draw, image: Image.Image, 
                      fields_config: List, template_design: Dict, 
                      asset: Asset, custom_data: Dict, font_sizes: Dict):
        """渲染字段到图像"""
        
        try:
            # 获取默认字体
            default_font = self._get_font(font_sizes['medium'])
            
            current_y = 5  # 起始Y位置
            line_height = font_sizes['medium'] + 2
            
            for field_config in fields_config:
                field_name = field_config.get('name')
                field_label = field_config.get('label', field_name)
                field_type = field_config.get('type', 'text')
                show_label = field_config.get('show_label', True)
                font_size = field_config.get('font_size', 'medium')
                
                # 获取字段值
                field_value = self._get_field_value(asset, field_name, custom_data)
                
                if not field_value and field_type not in ['qrcode', 'barcode']:
                    continue
                
                # 获取字体
                font = self._get_font(font_sizes.get(font_size, font_sizes['medium']))
                
                if field_type == 'qrcode':
                    current_y = self._render_qrcode(image, field_value, current_y, field_config)
                elif field_type == 'barcode':
                    current_y = self._render_barcode(image, field_value, current_y, field_config)
                else:
                    current_y = self._render_text_field(draw, field_label, field_value, 
                                                      current_y, font, show_label, image.width)
                
                current_y += 3  # 字段间距
                
                # 检查是否超出标签高度
                if current_y >= image.width - 10:
                    break
                    
        except Exception as e:
            logger.error(f"渲染字段失败: {e}")
            raise
    
    def _render_text_field(self, draw: ImageDraw.Draw, label: str, value: str, 
                          y: int, font: ImageFont, show_label: bool, max_width: int) -> int:
        """渲染文本字段"""
        
        try:
            text = f"{label}: {value}" if show_label else str(value)
            
            # 文本换行处理
            lines = self._wrap_text(text, font, max_width - 10)
            
            for line in lines:
                draw.text((5, y), line, fill='black', font=font)
                y += font.size + 2
            
            return y
            
        except Exception as e:
            logger.error(f"渲染文本字段失败: {e}")
            return y + 20
    
    def _render_qrcode(self, image: Image.Image, data: str, y: int, config: Dict) -> int:
        """渲染二维码"""
        
        try:
            # 创建二维码
            qr = qrcode.QRCode(
                version=1,
                error_correction=qrcode.constants.ERROR_CORRECT_L,
                box_size=2,
                border=1,
            )
            qr.add_data(data)
            qr.make(fit=True)
            
            # 生成二维码图像
            qr_img = qr.make_image(fill_color="black", back_color="white")
            
            # 调整二维码大小
            qr_size = min(60, image.width - y - 10)
            qr_img = qr_img.resize((qr_size, qr_size))
            
            # 粘贴到标签图像
            image.paste(qr_img, (5, y))
            
            return y + qr_size + 5
            
        except Exception as e:
            logger.error(f"渲染二维码失败: {e}")
            return y + 30
    
    def _render_barcode(self, image: Image.Image, data: str, y: int, config: Dict) -> int:
        """渲染条形码"""
        
        try:
            if not HAS_BARCODE:
                # Fallback: draw a simple placeholder box with text when python-barcode is unavailable
                draw = ImageDraw.Draw(image)
                placeholder_h = min(30, max(10, image.width - y - 10))
                # Right boundary: keep within canvas; image.height is horizontal extent here
                right_x = max(15, image.height - 5)
                draw.rectangle([(5, y), (right_x, y + placeholder_h)], outline='black', width=1)
                try:
                    draw.text((10, y + 5), f"BARCODE:{str(data)[:18]}", fill='black', font=self._get_font(12))
                except Exception:
                    pass
                return y + placeholder_h + 5

            # 创建条形码（依赖 python-barcode）
            code = Code128(data, writer=ImageWriter())
            # 生成条形码图像
            barcode_buffer = io.BytesIO()
            code.write(barcode_buffer)
            barcode_buffer.seek(0)

            barcode_img = Image.open(barcode_buffer)

            # 调整条形码大小
            barcode_width = min(image.height - 10, barcode_img.width)
            barcode_height = min(30, image.width - y - 10)
            barcode_img = barcode_img.resize((barcode_width, barcode_height))

            # 粘贴到标签图像
            image.paste(barcode_img, (5, y))

            return y + barcode_height + 5
            
        except Exception as e:
            logger.error(f"渲染条形码失败: {e}")
            return y + 30
    
    def _get_field_value(self, asset: Asset, field_name: str, custom_data: Dict = None) -> str:
        """获取字段值"""
        
        try:
            # 自定义数据优先
            if custom_data and field_name in custom_data:
                return str(custom_data[field_name])
            
            # 特殊字段处理
            if field_name == 'qr_code':
                return f"ASSET:{asset.asset_number}"
            elif field_name == 'barcode':
                return asset.asset_number
            elif field_name == 'current_date':
                return timezone.now().strftime('%Y-%m-%d')
            elif field_name == 'print_date':
                return timezone.now().strftime('%Y-%m-%d %H:%M')
            elif field_name == 'custom_text':
                return custom_data.get('custom_text', '') if custom_data else ''
            
            # 资产字段
            if hasattr(asset, field_name):
                value = getattr(asset, field_name)
                if value is None:
                    return ''
                return str(value)
            
            # 关联字段 (如 category__name)
            if '__' in field_name:
                parts = field_name.split('__')
                obj = asset
                for part in parts:
                    if hasattr(obj, part):
                        obj = getattr(obj, part)
                        if obj is None:
                            return ''
                    else:
                        return ''
                return str(obj)
            
            return ''
            
        except Exception as e:
            logger.error(f"获取字段值失败: {e}")
            return ''
    
    def _get_font(self, size: int) -> ImageFont:
        """获取字体"""
        
        try:
            # 尝试使用系统字体
            font_paths = [
                '/System/Library/Fonts/Arial.ttf',  # macOS
                'C:/Windows/Fonts/arial.ttf',       # Windows
                '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',  # Linux
            ]
            
            for font_path in font_paths:
                if os.path.exists(font_path):
                    return ImageFont.truetype(font_path, size)
            
            # 使用默认字体
            return ImageFont.load_default()
            
        except Exception as e:
            logger.warning(f"加载字体失败: {e}")
            return ImageFont.load_default()
    
    def _wrap_text(self, text: str, font: ImageFont, max_width: int) -> List[str]:
        """文本换行"""
        
        try:
            lines = []
            words = text.split(' ')
            current_line = ''
            
            for word in words:
                test_line = f"{current_line} {word}".strip()
                bbox = font.getbbox(test_line)
                text_width = bbox[2] - bbox[0]
                
                if text_width <= max_width:
                    current_line = test_line
                else:
                    if current_line:
                        lines.append(current_line)
                        current_line = word
                    else:
                        # 单词太长，强制换行
                        lines.append(word)
            
            if current_line:
                lines.append(current_line)
            
            return lines
            
        except Exception as e:
            logger.error(f"文本换行失败: {e}")
            return [text]
    
    def print_labels(self, print_job: LabelPrintJob) -> Dict:
        """打印标签"""
        
        try:
            print_job.status = 'printing'
            print_job.started_at = timezone.now()
            print_job.save()
            
            results = {
                'success': True,
                'total': 0,
                'printed': 0,
                'failed': 0,
                'errors': []
            }
            
            # 获取资产列表
            assets = Asset.objects.filter(id__in=print_job.asset_ids)
            results['total'] = len(assets) * print_job.copies
            
            # 获取打印机配置
            printer_config = self._get_printer_configuration()
            
            for asset in assets:
                for copy in range(print_job.copies):
                    try:
                        # 创建标签图像
                        label_image = self.create_label_image(print_job.template, asset)
                        
                        # 发送到打印机
                        success = self._send_to_printer(label_image, printer_config, print_job.template)
                        
                        # 记录打印历史
                        LabelPrintHistory.objects.create(
                            print_job=print_job,
                            asset=asset,
                            label_data={
                                'template_id': print_job.template.id,
                                'copy_number': copy + 1,
                                'print_settings': print_job.print_settings
                            },
                            is_successful=success,
                            error_message='' if success else '打印失败'
                        )
                        
                        if success:
                            results['printed'] += 1
                        else:
                            results['failed'] += 1
                            results['errors'].append(f"资产 {asset.asset_number} 打印失败")
                            
                    except Exception as e:
                        results['failed'] += 1
                        error_msg = f"资产 {asset.asset_number} 打印异常: {str(e)}"
                        results['errors'].append(error_msg)
                        logger.error(error_msg)
            
            # 更新打印任务状态
            print_job.total_labels = results['total']
            print_job.printed_labels = results['printed']
            print_job.failed_labels = results['failed']
            print_job.completed_at = timezone.now()
            
            if results['failed'] == 0:
                print_job.status = 'completed'
            elif results['printed'] == 0:
                print_job.status = 'failed'
                print_job.error_message = '; '.join(results['errors'])
            else:
                print_job.status = 'completed'  # 部分成功
            
            print_job.save()
            
            return results
            
        except Exception as e:
            print_job.status = 'failed'
            print_job.error_message = str(e)
            print_job.completed_at = timezone.now()
            print_job.save()
            logger.error(f"打印标签失败: {e}")
            return {
                'success': False,
                'error': str(e),
                'total': 0,
                'printed': 0,
                'failed': 0
            }
    
    def _get_printer_configuration(self) -> PrinterConfiguration:
        """获取打印机配置"""
        
        try:
            # 获取默认配置
            config = PrinterConfiguration.objects.filter(is_default=True, is_active=True).first()
            
            if not config:
                # 获取第一个可用配置
                config = PrinterConfiguration.objects.filter(is_active=True).first()
            
            if not config:
                # 创建默认配置
                config = PrinterConfiguration.objects.create(
                    name='默认PT-9500PC配置',
                    printer_model='pt-9500pc',
                    connection_type='usb',
                    is_default=True,
                    created_by_id=1  # 假设存在ID为1的用户
                )
            
            return config
        
        except Exception as e:
            logger.error(f"获取打印机配置失败: {e}")
            raise
    
    def _send_to_printer(self, image: Image.Image, config: PrinterConfiguration, template: LabelTemplate) -> bool:
        """根据连接类型发送到打印机。包含开发环境绕过以支持Windows。"""
        
        try:
            # 开发/Windows 环境绕过实际打印
            bypass_global = getattr(settings, 'DEV_BYPASS_PRINT', False)
            bypass_win = getattr(settings, 'DEV_BYPASS_PRINT_ON_WINDOWS', True)
            if bypass_global or (bypass_win and os.name == 'nt'):
                logger.info("开发环境绕过实际打印，标记为成功")
                return True
            
            # 保存临时文件
            with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as temp_file:
                image.save(temp_file.name, 'PNG')
                temp_path = temp_file.name
            
            try:
                # 根据连接类型分发
                if config.connection_type == 'usb':
                    return self._print_via_usb(temp_path, config, template)
                elif config.connection_type == 'network':
                    return self._print_via_network(temp_path, config, template)
                else:
                    logger.warning(f"不支持的连接类型: {config.connection_type}")
                    return False
            finally:
                # 清理临时文件
                try:
                    os.unlink(temp_path)
                except Exception:
                    pass
        
        except Exception as e:
            logger.error(f"发送到打印机失败: {e}")
            return False
    
    def _print_via_usb(self, image_path: str, config: PrinterConfiguration, template: LabelTemplate) -> bool:
        """通过USB打印"""
        
        try:
            # 构建打印命令 (这里需要根据实际的Brother PT-9500PC驱动来调整)
            cmd = [
                'lp',  # Linux/macOS 打印命令
                '-d', config.connection_params.get('device_name', 'Brother_PT-9500PC'),
                '-o', f'media={template.label_size}',
                '-o', 'fit-to-page',
                image_path
            ]
            
            # 执行打印命令
            result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
            
            if result.returncode == 0:
                logger.info(f"USB打印成功: {image_path}")
                return True
            else:
                logger.error(f"USB打印失败: {result.stderr}")
                return False
                
        except subprocess.TimeoutExpired:
            logger.error("USB打印超时")
            return False
        except Exception as e:
            logger.error(f"USB打印异常: {e}")
            return False
    
    def _print_via_network(self, image_path: str, config: PrinterConfiguration, template: LabelTemplate) -> bool:
        """通过网络打印"""
        
        try:
            # 网络打印实现 (需要根据具体网络协议实现)
            # 这里提供一个基础框架
            
            printer_ip = config.connection_params.get('ip_address')
            printer_port = config.connection_params.get('port', 9100)
            
            if not printer_ip:
                logger.error("缺少打印机IP地址")
                return False
            
            # 这里需要实现具体的网络打印协议
            # 可能需要使用Brother的SDK或者通用的打印协议
            
            logger.info(f"网络打印到 {printer_ip}:{printer_port}")
            return True
            
        except Exception as e:
            logger.error(f"网络打印异常: {e}")
            return False
    
    def test_printer_connection(self, config: PrinterConfiguration) -> Dict:
        """测试打印机连接"""
        
        try:
            if config.connection_type == 'usb':
                return self._test_usb_connection(config)
            elif config.connection_type == 'network':
                return self._test_network_connection(config)
            else:
                return {
                    'success': False,
                    'message': f'不支持的连接类型: {config.connection_type}'
                }
                
        except Exception as e:
            return {
                'success': False,
                'message': f'测试连接失败: {str(e)}'
            }
    
    def _test_usb_connection(self, config: PrinterConfiguration) -> Dict:
        """测试USB连接"""
        
        try:
            # 检查打印机是否可用
            cmd = ['lpstat', '-p']
            result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
            
            device_name = config.connection_params.get('device_name', 'Brother_PT-9500PC')
            
            if device_name in result.stdout:
                return {
                    'success': True,
                    'message': f'打印机 {device_name} 连接正常'
                }
            else:
                return {
                    'success': False,
                    'message': f'未找到打印机 {device_name}'
                }
                
        except Exception as e:
            return {
                'success': False,
                'message': f'USB连接测试失败: {str(e)}'
            }
    
    def _test_network_connection(self, config: PrinterConfiguration) -> Dict:
        """测试网络连接"""
        
        try:
            import socket
            
            printer_ip = config.connection_params.get('ip_address')
            printer_port = config.connection_params.get('port', 9100)
            
            if not printer_ip:
                return {
                    'success': False,
                    'message': '缺少打印机IP地址'
                }
            
            # 测试网络连接
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(5)
            
            result = sock.connect_ex((printer_ip, printer_port))
            sock.close()
            
            if result == 0:
                return {
                    'success': True,
                    'message': f'网络连接 {printer_ip}:{printer_port} 正常'
                }
            else:
                return {
                    'success': False,
                    'message': f'无法连接到 {printer_ip}:{printer_port}'
                }
                
        except Exception as e:
            return {
                'success': False,
                'message': f'网络连接测试失败: {str(e)}'
            }


# 创建全局服务实例
label_printer_service = BrotherPT9500PCService()